package com.unitd.modules.sso.controller;

import com.unitd.frame.comm.utils.BeanUtils;
import com.unitd.frame.comm.utils.StringUtils;
import com.unitd.frame.comm.utils.http.HttpUtils;
import com.unitd.frame.springweb.controller.SuperController;
import com.unitd.frame.sso.annotation.Action;
import com.unitd.frame.sso.annotation.Login;
import com.unitd.frame.sso.annotation.Permission;
import com.unitd.frame.sso.common.config.SSOConfig;
import com.unitd.frame.sso.common.encrypt.Salt;
import com.unitd.frame.sso.common.helper.IpHelper;
import com.unitd.frame.sso.common.helper.SSOHelper;
import com.unitd.frame.sso.common.token.SSOToken;
import com.unitd.frame.sso.web.waf.request.WafRequestWrapper;
import com.unitd.modules.sys.entity.SysUser;
import com.unitd.modules.sys.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @desc 登录控制器, 用户处理用户登录/登出
 * @filename IndexController.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2016/10/4
 */
@Controller
public class LoginController extends SuperController {

	private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

	private static final String LOCKSCREEN_USER_FLAG = "LockscreenUserFlag"; 			// 锁屏锁定用户标记

	@Autowired
	private ISysUserService sysUserService;

	/**
	 * @desc 登录(注解跳过权限验证)
	 * @param model 登录请求参数
	 * @return URL
	 */
//	@Log("处理用户登录操作")
	@Login(action = Action.Skip)
	@Permission(action = Action.Skip)
	@RequestMapping(value = "/login")
	public String doLogin(Model model, @RequestParam Map<String, Object> param) {
		logger.info("开始登录认证");

		// 获取重定向URL
		String returnUrl = request.getParameter(SSOConfig.getInstance().getParamReturl());

		// 只有post提交的请求才是真正的登录请求,否则get请求指示页面重定向
		if (isPost()) {
			// 获取当前请求Token
			SSOToken token = SSOHelper.getToken(request);

			// 判断是否登录,如果未登录则直接重定向到登录页,否则获取token中用户ID并转到首页
			if(BeanUtils.isNull(token)) {
				// 正常登录 需要过滤 XSS SQL 注入
				WafRequestWrapper wr = new WafRequestWrapper(request);
				String userName = wr.getParameter("userName"); 							// 登录请求中的登录用户名

				if (StringUtils.isNotBlank(userName)) {
					String userPwd = wr.getParameter("userPwd"); 						// 登录密码
					if (StringUtils.isBlank(userPwd)) {
						return this.toLogin(model, "登录密码不能为空!", returnUrl); 	// 密码不正确
					}

					// 查询用户信息,并验证用户信息的有效性
					SysUser user = sysUserService.findByUserName(userName);

					// 账号不存在
					if (user == null) {
						return this.toLogin(model, "登录用户不存在,请重新输入!", returnUrl);
					}
					// 密码错误(密码利用登录用户名作为动态盐值和输入的密码进行加密后存入数据库中)
					if(!Salt.md5SaltValid(userName, user.getUserPwd(), userPwd)) {
						return this.toLogin(model, "账号或密码不匹配,请重新输入!", returnUrl);
					}
					// 账号锁定
					if ("1".equals(user.getState())) {
						return this.toLogin(model, "账号已被锁定,请联系管理员!", returnUrl);
					}
					// 账号已被删除
					if ("2".equals(user.getState())) {
						return this.toLogin(model, "账号已被删除,请联系管理员!", returnUrl);
					}

					/* 用户存在,签名合法,登录成功;则设置登录token信息 */
					token = new SSOToken(request, "1000");
					token.setId(user.getId());  											// 实际生产环境中,该ID可以为用户的主键
					token.setUid(user.getId()); 											// 登录用户Id
					token.setUname(user.getUserName()); 									// 当前的登录用户名
					token.setOrgNo(user.getOrgNo()); 										// 当前登录用户所在机构编号
					token.setOrgName(user.getOrgName()); 									// 当前登录用户所在机构名称
					token.setData(user.getUserName()); 										// 用户名称作为说明信息(这里可以随意组装信息)

					// 记住密码
					if ( "on".equals(wr.getParameter("rememberMe")) ) {
						// 设置登录 Cookie 时长1周 = 604800 秒; 动态设置 maxAge 实现记住密码功能
						request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, 604800);
					}
					// 设置登录 Cookie; 最后一个参数为true时添加cookie的同时销毁当前JSESSIONID并重新创建信任的JSESSIONID
					SSOHelper.setSSOCookie(request, response, token, true);

					// 登陆成功,设置最后登陆ip和登陆时间
					Map<String, Object> userDataMap = new HashMap<>(2);
					userDataMap.put("userId", user.getId());
					userDataMap.put("loginIp", IpHelper.getIpAddr(request));
					sysUserService.updateLoginDate(userDataMap);
				} else {
					return this.toLogin(model, "用户名不能为空!", returnUrl); 			// 用户名不能为空
				}
			}
			// 重定向到指定地址 returnUrl
			if (StringUtils.isBlank(returnUrl)) {
				returnUrl = "/";
			} else {
				returnUrl = HttpUtils.decodeURL(returnUrl);
			}
			return redirectTo(returnUrl);
		}

		if (StringUtils.isNotEmpty(returnUrl)) {
			model.addAttribute("ReturnURL", returnUrl);
		}
		return "/login";
	}

	/**
	 * @desc 统一退出,调用对外提供退出的所有接口
	 * 如果实现了SSOCache 缓存,则SSO组件会自动缓存token当需要推出token时,只需要调用SSOHelper.clearLogin(request, response);方法
	 * 此方法会自动清理token缓存信息,同时各个通过SSO登录了的系统也会自动退出。建议退出登录的时候统一调用此方法;
	 * 如果你不这么干那么你只能挨个不同域退出一遍,最终全站退出。
	 * 如果只是退出登录系统,且不清空缓存,则使用 SSOHelper.logout(request, response);
	 * @return 退出之后的URL
	 */
//	@Log("处理用户登出操作")
	@Login(action = Action.Skip)
	@Permission(action = Action.Skip)
	@RequestMapping("/logout")
	public String logout() {
		// SSO 退出登录
		SSOHelper.clearLogin(request, response);
		return redirectTo("/login.html");
	}

	/**
	 * @desc 注册新用户
	 * @param model SpringMVC前端参数对象
	 * @param user 当前注册的用户信息
	 * @return 注册页面
	 */
//	@Log("处理用户注册操作")
	@Login(action = Action.Skip)
	@Permission(action = Action.Skip)
	@RequestMapping("/register")
	public String register(Model model, SysUser user) {
		if (isPost()) {
			SysUser existUser = sysUserService.findByUserName(user.getUserName());
			if(BeanUtils.isNull(existUser)) {
				// 用户名作为密码盐值,与前端输入的密码一起进行盐值加密
				user.setUserPwd(Salt.md5SaltEncode(user.getUserName(), user.getUserPwd()));
				user.setCreateDate(new Date());
				user.setLoginDate(user.getCreateDate());
				int rlt = sysUserService.insertSelective(user);
				if (rlt > 0) {
					// 注册成功,自动登录进入后台
					SSOToken st = new SSOToken(request);
					st.setId(user.getId());
					st.setData(user.getUserName());
					SSOHelper.setSSOCookie(request, response, st, true);
					return redirectTo("/index.html");
				}
			} else {
				model.addAttribute("tipMsg", "注册用户名【" + user.getUserName() + "】已存在！");
			}
		}
		return "/register";
	}

	/**
	 * @desc 锁屏及锁屏登录
	 * @param model SpringMVC前端参数对象
	 * @param password 当前登录密码
	 */
//	@Log("处理用户锁屏及锁屏登录操作")
	@Login(action = Action.Skip)
	@Permission(action = Action.Skip)
	@RequestMapping("/lockscreen")
	public String lockscreen(Model model, String password) {
		HttpSession session = request.getSession();
		String userName = (String) session.getAttribute(LOCKSCREEN_USER_FLAG); 			// 取出session中的登录用户名称

		// 登录用户名为空,则说明session中不存在,这时需要去token中获取看是否存在对应的登录用户名信息,如果不存在则直接跳转到登录页面,执行重新登录操作
		if (StringUtils.isBlank(userName)) {
			SSOToken st = SSOHelper.getToken(request);

			// token不存在,直接重定向到登录页面
			if (BeanUtils.isNull(st)) {
				return redirectTo("/login.html");
			}

			// token存在,取出token中的登录用户名信息后,清理token缓存信息
			userName = st.getData();
			session.setAttribute(LOCKSCREEN_USER_FLAG, userName);
			SSOHelper.clearLogin(request, response);

		} else if (StringUtils.isNotBlank(password) && isPost()) {
			// 锁定页面登录
			SysUser user = sysUserService.findByUserName(userName);

			if (user != null && Salt.md5SaltValid(userName, user.getUserPwd(), password)) {
				// 登录成功,进入后台
				SSOToken st = new SSOToken(request);
				st.setId(user.getId());
				st.setData(userName);
				SSOHelper.setSSOCookie(request, response, st, true);
				return redirectTo("/");
			}
		}

		model.addAttribute("userName", userName);
		return "/lockscreen";
	}

	/**
	 * @desc 校验验证码是否有效
	 * @param validateCode 前端输入的验证码
	 * @return 验证码是否有效
	 */
	private boolean validateCaptchaCode (String validateCode) {
		String kaptchaExpected = (String)request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
		return (StringUtils.isNotBlank(validateCode) && StringUtils.equalsIgnoreCase(kaptchaExpected, validateCode));
	}

	/**
	 * @desc 登录处理时,对于登录信息验证失败时,需要提示错误信息,并重新返回到登录页面
	 * @param model SpringMVC前端对象
	 * @param errMsg 错误消息
	 * @param returnUrl 需要重定向到的页面
	 * @return 登录页面
	 */
	private String toLogin(Model model, String errMsg, String returnUrl) {
		model.addAttribute("errorMsg", errMsg); 													// 设置错误信息

		if (StringUtils.isNotEmpty(returnUrl)) {
			model.addAttribute("ReturnURL", returnUrl);
		}
		return "/login";
	}

	public static void main(String[] args) {
		System.out.println(Salt.md5SaltEncode("admin", "123456"));
	}
}